package simulacao;

import java.util.ArrayList;

import tratamentoExcecoes.Excecao;
import tratamentoExcecoes.MensagemExcecao;
import entradaSaida.EscritorArquivo;
import entradaSaida.EscritorPlanilha;
import entradaSaida.EscritorTxt;

public class Jogo {
	
	private ArrayList<Jogador> jogadores;
	private int numRodadas;
	private boolean simultaneo;
	private double p;
	private double taxaJuros;
	private boolean salvarSaida;
	private boolean planilha;
	private boolean modoHistograma;
	private EscritorArquivo escritorArquivo;
	private boolean DEBUG = false;
	private TabelaPayoffs tabelaPayoffs;

	public Jogo(){
		jogadores = new ArrayList<Jogador>();
		escritorArquivo = null;
	}
	
	public ResultadoSimulacao distribuicaoGeometrica(){
		double taxaAcumulada = 1.0;
		double[] payoffsEsperados = new double[jogadores.size()];
		boolean saida = false, infinito = false;
		int rodada;
		
		Jogador jogadorA = getJogador(0);
		Jogador jogadorB = getJogador(1);
		
		for(rodada = 0; !saida; rodada++){
			if(DEBUG) System.out.println("Rodada "+rodada);
			int jogadaDeA, jogadaDeB;
			jogadaDeA = jogadorA.getJogada();
			jogadaDeB = jogadorB.getJogada();
			
			if(DEBUG)System.out.println("Jogada de A: "+jogadaDeA+"\nJogada de B: "+jogadaDeB);
			if(!simultaneo){
				//Se não for simultâneo o segundo jogador pode retaliar na mesma rodada
			}
			payoffsEsperados[0] += taxaAcumulada*tabelaPayoffs.getPayoff(jogadaDeA, jogadaDeB, 0);
			payoffsEsperados[1] += taxaAcumulada*tabelaPayoffs.getPayoff(jogadaDeA, jogadaDeB, 1);
			if(DEBUG)System.out.println("Payoff de A: "+payoffsEsperados[0]+"\nPayoff de B: "+payoffsEsperados[1]);
			jogadorA.adicionaJogadaAdversario(jogadaDeB);
			jogadorB.adicionaJogadaAdversario(jogadaDeA);
			taxaAcumulada = taxaAcumulada*(1.0/(1.0+taxaJuros));
			saida = verificaSaida(rodada, taxaAcumulada);
			if(infinito){
				payoffsEsperados[0] = taxaAcumulada*tabelaPayoffs.getPayoff(jogadaDeA, jogadaDeB, 0)*Double.POSITIVE_INFINITY;
				payoffsEsperados[1] = taxaAcumulada*tabelaPayoffs.getPayoff(jogadaDeA, jogadaDeB, 1)*Double.POSITIVE_INFINITY;
				break; //Jogo infinito
			}
		}
		return new ResultadoSimulacao(rodada,payoffsEsperados); 
	}
	
	public double[] simular (int quantidadeSimulacoes){
		
		tabelaPayoffs = TabelaPayoffs.getInstance();
		double[] valores = new double[3];
		double[] somatorioSimulacoes = new double[2];
		TabelaResultados tabelaResultados = new TabelaResultados();
		if(salvarSaida && escritorArquivo==null){
			try{
				if(planilha)	escritorArquivo = new EscritorPlanilha(false);
				else			escritorArquivo = new EscritorTxt();
			} catch(Exception e){
				Excecao.exibirExcecao(MensagemExcecao.ERRO_CAMINHO_ARQUIVO, e);
			}
		}
		if(escritorArquivo==null) salvarSaida = false;
		
		long ini = System.currentTimeMillis();
		for(int i = 0; i<quantidadeSimulacoes ; i++){
			getJogador(0).getEstrategia().reiniciarEstrategia();
			getJogador(1).getEstrategia().reiniciarEstrategia();
			ResultadoSimulacao resultado = distribuicaoGeometrica();
			tabelaResultados.adicionaResultado(resultado);
			for(int j = 0; j < getJogadores().size(); j++){
				getJogador(j).adicionaPayoff(resultado.getPayoffs()[j]);
				somatorioSimulacoes[j] += resultado.getPayoffs()[j];
			}
			if(DEBUG) System.out.println("Simulação" +i);
		}
		
		if(salvarSaida){
			try {
				String dados[] = {"Rodadas","PayoffA","PayoffB"};
				escritorArquivo.escreverCabecalho(p, taxaJuros, quantidadeSimulacoes, 0, dados);
				if(planilha){
					try {
						EscritorPlanilha xls = ((EscritorPlanilha)escritorArquivo);
						xls.preencherSerieDados(getJogador(0).getHistograma(), "A", 5,quantidadeSimulacoes);
						xls.preencherSerieDados(getJogador(1).getHistograma(), "D", 5,quantidadeSimulacoes);
					} catch (Exception e) {
						Excecao.exibirExcecao(MensagemExcecao.ERRO_ESCREVER_ARQUIVO, e);
					}
				} else if(modoHistograma){
					for(int j = 0; j < getJogadores().size(); j++){
						escritorArquivo.escreverNoArquivo("\nResultados do Jogador "+ (j+1) +"\n\n"
															+getJogador(j).getHistograma().toString());
						escritorArquivo.escreverNoArquivo("-----------------------------------------");
					}
				} else {
					escritorArquivo.escreverNoArquivo(tabelaResultados.toString());
				}
			} catch (Exception e) {
				Excecao.exibirExcecao(MensagemExcecao.ERRO_ESCREVER_ARQUIVO, e);
			}
			try {
				escritorArquivo.fecharArquivo();
			} catch (Exception e) {
				Excecao.exibirExcecao(MensagemExcecao.ERRO_FECHAR_ARQUIVO, e);
			}
		}

		//Impressão dos resultados
		for(int j = 0; j < getJogadores().size(); j++){
			double esperancaPayoff = somatorioSimulacoes[j]/(double)quantidadeSimulacoes;
			valores[j+1] = esperancaPayoff; 
		}
		long tempoExecucao = (System.currentTimeMillis()-ini);
		valores[0] = tempoExecucao/1000.0;
		return valores;
	}
	
	/**
	 * Determina o fim do jogo se o sorteio da probabilidade indicar o término, ou se a taxa de desconto
	 * for tão pequena que os payoffs já não façam diferença, ou se atingir o limite do número de rodadas.
	 */
	private boolean verificaSaida(int rodada, double taxaAcumulada){
		double rand = Math.random();
		if(rand < this.p || taxaAcumulada < 1E-7 || (numRodadas > 0 && rodada >= numRodadas-1))
			return true;
		else
			return false;
	}
	
	public void addJogador(Jogador jogador){
		jogadores.add(jogador);
		jogador.setNumJogador(jogadores.indexOf(jogador));
	}
	
	public Jogador getJogador(int index){
		return jogadores.get(index);
	}
	
	public ArrayList<Jogador> getJogadores(){
		return this.jogadores;
	}
	
	public int getNumRodadas() {
		return numRodadas;
	}

	public void setNumRodadas(int numRodadas) {
		this.numRodadas = numRodadas;
	}

	public void setSalvarSaida(boolean salvarSaida) {
		this.salvarSaida = salvarSaida;
	}

	public void setPlanilha(boolean planilha) {
		this.planilha = planilha;
	}

	public void setModoHistograma(boolean modoHistograma) {
		this.modoHistograma = modoHistograma;
	}

	public void setSimultaneo(boolean simultaneo) {
		this.simultaneo = simultaneo;
	}

	public double getP() {
		return p;
	}

	public void setP(double p) {
		this.p = p;
	}

	public double getTaxaJuros() {
		return taxaJuros;
	}

	public void setTaxaJuros(double taxaJuros) {
		this.taxaJuros = taxaJuros;
	}

	public EscritorArquivo getEscritorArquivo() {
		return escritorArquivo;
	}

	public void setEscritorArquivo(EscritorArquivo escritorArquivo) {
		this.escritorArquivo = escritorArquivo;
	}
	
	public double getTaxaDesconto(){
		if (taxaJuros==0 && p==0.0){
			if(numRodadas==0){
				return Double.POSITIVE_INFINITY;
			} else {
				return 0.0;
			}
		} else {
			return (1.0-p)/(1.0+taxaJuros);
		}
	}

}
